using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using XenoGears.Collections.Dictionaries;
using XenoGears.Functional;
using XenoGears.Assertions;

namespace Jink.Core.Json.Values
{
    public abstract class JsonValue : BaseDictionary<String, JsonValue>
    {
        public JsonValue Parent { get; set; }
        public IEnumerable<JsonValue> Parents { get { return Parent.Unfold(j => j.Parent, j => j != null); } }
        private IList<KeyValuePair<String, JsonValue>> HashTable { get; set; }

        protected JsonValue(bool isPrimitive)
            : this(isPrimitive ? null : new List<KeyValuePair<String, JsonValue>>())
        {
        }

        protected JsonValue(IEnumerable<KeyValuePair<String, JsonValue>> hashTable)
        {
            HashTable = hashTable == null ?
                new List<KeyValuePair<String, JsonValue>>().ToReadOnly() :
                (IList<KeyValuePair<String, JsonValue>>)hashTable.ToList();

            // todo. implement observable list that would track parents automatically
            HashTable.ForEach(kvp => kvp.Value.Parent = this);
        }

        #region String conversion routines

        public abstract void BuildCompactString(TextWriter writer);
        public abstract void BuildPrettyString(IndentedTextWriter writer);

        public String ToCompactString() 
        {
            var buffer = new StringBuilder();
            BuildCompactString(new StringWriter(buffer));
            return buffer.ToString();
        }

        public String ToPrettyString()
        {
            var buffer = new StringBuilder();
            BuildPrettyString(new IndentedTextWriter(new StringWriter(buffer)));
            return buffer.ToString();
        }

        public sealed override String ToString()
        {
            return ToCompactString();
        }

        #endregion

        #region String => JsonValue implementation

        public sealed override int Count
        {
            get { return HashTable.Count; }
        }

        public sealed override IEnumerator<KeyValuePair<String, JsonValue>> GetEnumerator()
        {
            return HashTable.GetEnumerator();
        }

        public sealed override bool ContainsKey(String key)
        {
            return HashTable.Any(kvp => kvp.Key == key);
        }

        public sealed override bool TryGetValue(String key, out JsonValue value)
        {
            if (ContainsKey(key))
            {
                value = HashTable.Single(kvp => kvp.Key == key).Value;
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }

        public sealed override bool IsReadOnly
        {
            get { return HashTable.IsReadOnly; }
        }

        public sealed override void Add(String key, JsonValue value)
        {
            ContainsKey(key).AssertFalse();
            HashTable.Add(new KeyValuePair<String, JsonValue>(key, value));
        }

        public sealed override bool Remove(String key)
        {
            var index = HashTable.IndexOf(kvp => kvp.Key == key);
            if (index == -1)
            {
                return false;
            }
            else
            {
                HashTable.RemoveAt(index);
                return true;
            }
        }

        public sealed override void Clear()
        {
            HashTable.Clear();
        }

        protected sealed override void SetValue(String key, JsonValue value)
        {
            var index = HashTable.IndexOf(kvp1 => kvp1.Key == key);
            var kvp = HashTable[index];
            HashTable[index] = new KeyValuePair<String, JsonValue>(kvp.Key, value);
        }

        #endregion

        #region int => JsonValue implementation

        public void Add(JsonValue item)
        {
            Insert(Count, item);
        }

        public bool Contains(JsonValue item)
        {
            return Values.Contains(item);
        }

        public bool Remove(JsonValue item)
        {
            var index = IndexOf(item);
            if (index == -1)
            {
                return false;
            }
            else
            {
                HashTable.RemoveAt(index);
                return true;
            }
        }

        public int IndexOf(JsonValue item)
        {
            return HashTable.IndexOf(kvp => kvp.Value == item);
        }

        public void Insert(int index, JsonValue item)
        {
            HashTable.Insert(index, new KeyValuePair<String, JsonValue>(index.ToString(), item));
        }

        public void RemoveAt(int index)
        {
            HashTable.RemoveAt(index);
        }

        public JsonValue this[int index]
        {
            get { return this[HashTable[index].Key]; }
            set { this[HashTable[index].Key] = value; }
        }

        #endregion

        #region Equality convenience operators

        public sealed override bool Equals(Object @object)
        {
            return ToString() == @object.ToJson().ToString();
        }

        public sealed override int GetHashCode()
        {
            return ToString().GetHashCode();
        }

        public static bool operator ==(JsonValue value, Object @object)
        {
            if (Object.ReferenceEquals(value, null))
            {
                return Object.ReferenceEquals(@object, null);
            }
            else
            {
                return value.Equals(@object);
            }
        }

        public static bool operator !=(JsonValue value, Object @object)
        {
            return !(value == @object);
        }

        public static bool operator ==(Object @object, JsonValue value)
        {
            return value == @object;
        }

        public static bool operator !=(Object @object, JsonValue value)
        {
            return value != @object;
        }

        public static bool operator ==(JsonValue value1, JsonValue value2)
        {
            return value1 == (Object)value2;
        }

        public static bool operator !=(JsonValue value1, JsonValue value2)
        {
            return value1 != (Object)value2;
        }

        #endregion
    }
}